`timescale 1ns/1ps
`default_nettype none

/* NOTE:
* - 向通用芯片配置寄存器
* - 每60帧选取1帧进行配置。
* - 配置时，占用第一个最低比特的串移数据，load和oe
*/

`define GetPwmBits(lsb, num) I_cfg_pwm_setting[lsb+num-1:lsb]

module cxy_general_wr_reg
    #(
    parameter   DW          = 96
    )
    (
    // system signal
    input  wire             I_sclk,
    input  wire             I_rst_n,
    // enable
    input  wire             I_enable,
    // config
    input  wire [3:0]       I_cfg_gamma_bit,    // gamma bit数, 0 - 1bit, ..., 15 - 16bit
    input  wire [9:0]       I_cfg_scan_length,  //一扫的像素数量（包含虚点） 
    input  wire [255:0]     I_cfg_pwm_setting,  // pwm芯片设置
    // read request
    input  wire             I_read_req,         // 读请求
    input  wire             I_read_busy,        // 读忙碌
    input  wire [3:0]       I_read_bit_sel,     // 读取的bit选择
    // frame
    input  wire             I_frame_sync,
    // 正常显示模块产生的led信号
    input  wire             I_clock_in,
    input  wire             I_load_in,
    input  wire             I_oe_in,            // 输入的oe_in来自pixel_display_general，总是高有效
    // led signal
    output wire             O_load_vld,
    output wire             O_load_out,

    output wire             O_oe_vld,
    output wire             O_oe_out,

    output wire             O_data_vld,
    output wire [DW-1:0]    O_data_out
    );
//------------------------Parameter----------------------
// fsm
localparam [2:0]
    IDLE            = 0,
    WAIT_VLD_FRM    = 1,    //等待配置寄存器的帧.N帧中才有1帧用于配置寄存器
    WAIT_LOWEST_BIT = 2,    //等待一帧中第一个bit0的串移周期
    NOP1            = 3,
    PREP            = 4,
    SHIFT_REG       = 5,
    NOP2            = 6,
    WAIT_OE_INVLD   = 7;

localparam
    FRM_GAP         = 60;

//------------------------Local signal-------------------
// fsm
reg  [2:0]      state;

reg  [7:0]      frm_cnt;
reg  [3:0]      read_bit_sel;

reg             clock_in_sr;
reg             load_in_sr;
reg             oe_in_sr;

wire            clock_in_negedge;
wire            load_in_posedge;
wire            load_in_negedge;
wire            oe_in_negedge;

reg  [9:0]      shift_cnt;
reg  [1:0]      cfg_reg_cnt;
reg  [9:0]      load_pos;

reg             load_vld;
reg             load_out;
reg             oe_vld;
reg             data_vld;
reg  [15:0]     port0_reg;
reg  [15:0]     port1_reg;
reg  [15:0]     port2_reg;

// pwm_setting
wire [1:0]      cfg_reg_num;
wire [3:0]      cfg_reg1_load;
wire [3:0]      cfg_reg2_load;
wire [15:0]     cfg_port0_reg1;
wire [15:0]     cfg_port0_reg2;
wire [15:0]     cfg_port1_reg1;
wire [15:0]     cfg_port1_reg2;
wire [15:0]     cfg_port2_reg1;
wire [15:0]     cfg_port2_reg2;

//------------------------Instantiation------------------

//------------------------Body---------------------------
                
//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
//state[2:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        state <= IDLE;
    else
    case(state)
        IDLE:   
            if(cfg_reg_num>0)
                state <= WAIT_VLD_FRM;

        WAIT_VLD_FRM:
            if(I_frame_sync && frm_cnt==FRM_GAP)
                state <= WAIT_LOWEST_BIT;

        WAIT_LOWEST_BIT:
            if(I_read_req && !I_read_busy && read_bit_sel==15-I_cfg_gamma_bit)
                state <= NOP1;

        NOP1:   state <= PREP;

        PREP:
                state <= SHIFT_REG;

        SHIFT_REG:
            if(load_in_negedge)
                state <= NOP2;

        NOP2:   state <= WAIT_OE_INVLD;

        WAIT_OE_INVLD:
            if(oe_in_negedge)
                state <= IDLE;

        default:
                state <= IDLE;
    endcase

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//frm_cnt[7:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        frm_cnt <= 'b0;
    else if(I_frame_sync)
    begin
        if(frm_cnt==FRM_GAP)
            frm_cnt <= 'b0;
        else
            frm_cnt <= frm_cnt + 1'b1;
    end

//read_bit_sel[3:0]
always@(posedge I_sclk)
    if(I_read_req && !I_read_busy)
        read_bit_sel <= I_read_bit_sel;

//clock_in_sr
//load_in_sr
//oe_in_sr
always@(posedge I_sclk)
    begin
        clock_in_sr <= I_clock_in;
        load_in_sr  <= I_load_in;
        oe_in_sr    <= I_oe_in;
    end

assign clock_in_negedge = ({clock_in_sr,I_clock_in}==2'b10);
assign load_in_posedge  = ({load_in_sr ,I_load_in }==2'b01);
assign load_in_negedge  = ({load_in_sr ,I_load_in }==2'b10);
assign oe_in_negedge    = ({oe_in_sr   ,I_oe_in   }==2'b10);

//shift_cnt[9:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        shift_cnt <= 'b0;
    else if(state==SHIFT_REG)
    begin
        if(clock_in_negedge)
            shift_cnt <= shift_cnt + 1'b1;
    end
    else
        shift_cnt <= 'b0;

//cfg_reg_cnt[1:0]
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        cfg_reg_cnt <= 'b0;
    else if(state==NOP1)
        cfg_reg_cnt <= cfg_reg_cnt + 1'b1;
    else if(state==NOP2 && cfg_reg_cnt==cfg_reg_num)
        cfg_reg_cnt <= 'b0;

//load_pos[9:0]
always@(posedge I_sclk)
    if(state==PREP)
    case(cfg_reg_cnt)
        1:  load_pos <= I_cfg_scan_length-1'b1-cfg_reg1_load;
        2:  load_pos <= I_cfg_scan_length-1'b1-cfg_reg2_load;
    endcase

//{{{+++++++++++++++++++++out++++++++++++++++++++++++++++
assign O_load_vld = load_vld;
assign O_oe_vld   = oe_vld;
assign O_data_vld = data_vld;

assign O_load_out = (!I_enable) ? 1'b0 : load_out;
assign O_oe_out   = 'b0;
assign O_data_out = {(DW/3){port2_reg[15],port1_reg[15],port0_reg[15]}};

//load_vld
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        load_vld <= 'b0;
    else if(state==SHIFT_REG && clock_in_negedge)
        load_vld <= 1'b1;
    else if(state==NOP2)
        load_vld <= 'b0;

//load_out
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        load_out <= 'b0;
    else if(state==SHIFT_REG)
    begin
        if(clock_in_negedge && shift_cnt==load_pos)
            load_out <= 1'b1;
        else if(shift_cnt==I_cfg_scan_length)
            load_out <= 'b0;
    end
    else
        load_out <= 'b0;

//oe_vld
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        oe_vld <= 'b0;
    else if(state==SHIFT_REG && load_in_posedge)
        oe_vld <= 1'b1;
    else if(oe_in_negedge)
        oe_vld <= 'b0;

//data_vld
always@(posedge I_sclk or negedge I_rst_n)
    if(!I_rst_n)
        data_vld <= 'b0;
    else if(state==PREP || state==SHIFT_REG)
        data_vld <= 1'b1;
    else
        data_vld <= 'b0;

//portx_reg[15:0]
always@(posedge I_sclk)
    if(state==PREP)
        case(cfg_reg_cnt)
            1:  begin
                    port0_reg <= cfg_port0_reg1;
                    port1_reg <= cfg_port1_reg1;
                    port2_reg <= cfg_port2_reg1;
                end

            2:  begin
                    port0_reg <= cfg_port0_reg2;
                    port1_reg <= cfg_port1_reg2;
                    port2_reg <= cfg_port2_reg2;
                end
        endcase
    else if(state==SHIFT_REG && clock_in_negedge)
        begin
            port0_reg <= {port0_reg[14:0],port0_reg[15]};
            port1_reg <= {port1_reg[14:0],port1_reg[15]};
            port2_reg <= {port2_reg[14:0],port2_reg[15]};
        end

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++pwm setting++++++++++++++++++++
assign cfg_reg_num     = `GetPwmBits(0, 2);
assign cfg_reg1_load   = `GetPwmBits(8, 4);
assign cfg_reg2_load   = `GetPwmBits(16, 4);
assign cfg_port0_reg1  = `GetPwmBits(40, 16);
assign cfg_port0_reg2  = `GetPwmBits(56, 16);
assign cfg_port1_reg1  = `GetPwmBits(72, 16);
assign cfg_port1_reg2  = `GetPwmBits(88, 16);
assign cfg_port2_reg1  = `GetPwmBits(104, 16);
assign cfg_port2_reg2  = `GetPwmBits(120, 16);

// MBI5122 = MBI5124
//assign cfg_reg_num     = 2'd1;
//assign cfg_reg1_load   = 4'd4;
//assign cfg_reg2_load   = 'b0;
//assign cfg_port0_reg1  = 16'h7D6B;
//assign cfg_port0_reg2  = 'b0;
//assign cfg_port1_reg1  = 16'hF16B;
//assign cfg_port1_reg2  = 'b0;
//assign cfg_port2_reg1  = 16'hED6B;
//assign cfg_port2_reg2  = 'b0;

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
